package com.spudtech.utils
{
    
    import flash.geom.*;
    import flash.events.*;
    
    import flash.utils.Dictionary;
    
    import com.spudtech.*;
    
    
    public class SpudInput
    {
        // Singleton class that registers input
        
        protected static var _inputMgr : SpudInput;
        
        protected var _keys : SpudKey;
        
        protected var _inputState : Dictionary;
        protected var _controlNames : Array;
        
        protected var _mousePos : Point;
        
        
        public function SpudInput(key_obj : SpudKey)
        {
            _keys = key_obj;
            
            _inputState = new Dictionary();
            
            _controlNames = new Array();
            
            _mousePos = new Point(0,0);
            
            SpudGame.mainApp.stage.addEventListener(KeyboardEvent.KEY_DOWN,this.onKeyDown);
            SpudGame.mainApp.stage.addEventListener(KeyboardEvent.KEY_UP,this.onKeyUp);
            
            SpudGame.mainApp.stage.addEventListener(MouseEvent.MOUSE_DOWN,this.onMouseDown);
            SpudGame.mainApp.stage.addEventListener(MouseEvent.MOUSE_UP,this.onMouseUp);
            SpudGame.mainApp.stage.addEventListener(MouseEvent.MOUSE_WHEEL,this.onMouseWheel);
            SpudGame.mainApp.stage.addEventListener(MouseEvent.MOUSE_MOVE,this.onMouseMove);
            
        }
        
        public static function init(key_obj : SpudKey) : void
        {
            // Implement in derived classes
        }
        
        public static function get inputMgr() : SpudInput
        {
            return _inputMgr;
        }
        
        public function get mousePos() : Point
        {
            return _mousePos;
        }
        
        public function get inputState() : Dictionary
        {
            return _inputState;
        }
        
        public function get keys() : SpudKey
        {
            return _keys;
        }
        
        
        public function register(control_name : *, ...controls) : void
        {
            if (controls.length == 0)
            {
                // We weren't given any controls
                throw new ArgumentError("No key codes given");
            }
            
            control_name = String(control_name).toLowerCase();
            
            var control_codes : Array = new Array();
            
            for each (var item : * in controls)
            {
                if (item is int)
                {
                    if (item >= 0)
                    {
                        // It is a non-negative
                        // integer
                        control_codes.push(int(item))
                    }
                    else
                    {
                        // It is negative
                        throw new RangeError("Invalid key code (Key codes may not be negative)");
                    }
                }
                else
                {
                    // It is not an integer
                    throw new TypeError("Invalid key code (That is *not* an integer)");
                }
            }
            
            if (inputState[control_name] == null)
            {
                inputState[control_name] = new Dictionary;
                
                inputState[control_name]["State"] = [false,false];
                
                inputState[control_name]["Keys"] = new Array();
                
                _controlNames.push(control_name)
                
            }
            
            inputState[control_name]["Keys"] = inputState[control_name]["Keys"].concat(control_codes);
            
        }
        
        public function unRegister(control_name : *, ...controls) : void
        {
            if (controls.length == 0)
            {
                // Unlike for register(), in this
                // method an empty control list
                // is valid
                // Doing this just removes *every*
                // key code for that input name
                
                var matches_name : Function = function(item : *, index : int, array : Array) : Boolean
                {
                    if (String(item) == control_name)
                    {
                        // That's the name
                        // Return false so we remove it
                        return false;
                    }
                    else
                    {
                        // That's not the name
                        // Return true so we keep it
                        return true;
                    }
                }
                
                _inputState[control_name] = null;
                
                _controlNames = _controlNames.filter(matches_name);
                
                return;
                
            }
            
            control_name = String(control_name).toLowerCase();
            
            var control_codes : Array = new Array();
            
            for each (var item : * in controls)
            {
                if (item is int)
                {
                    if (item >= 0)
                    {
                        // It is a non-negative
                        // integer
                        control_codes.push(int(item))
                    }
                    else
                    {
                        // It is negative
                        throw new RangeError("Invalid key code (Key codes may not be negative)");
                    }
                }
                else
                {
                    // It is not an integer
                    throw new TypeError("Invalid key code (That is *not* an integer)");
                }
            }
            
            
            // Test whether a given code is
            // in the Array of codes we want
            // to remove
            var should_keep : Function = function(item : *, index : int, array : Array) : Boolean
            {
                if (control_codes.indexOf(item) != -1)
                {
                    // It's in the list of
                    // key codes to remove
                    // Return false to discard it
                    return false;
                }
                else
                {
                    // It's not in the list
                    // of key code to remove
                    // Return true to keep it
                    return true;
                }
                
            }
            
            control_codes[control_name]["Keys"] = control_codes[control_name]["Keys"].filter(should_keep);
            
            if (control_codes[control_name]["Keys"].length == 0)
            {
                // We don't have anything registered for
                // that control code, so we remove the
                // control
                
                // We call this method without any
                // key codes to remove the control
                // completely
                unRegister(control_name);
                
            }
            
        }
        
        
        public function onKeyDown(event : KeyboardEvent) : void
        {
            for each (var ctrl_name : String in _controlNames)
            {
                for each (var ctrl_code : int in _inputState[ctrl_name]["Keys"])
                {
                    if (ctrl_code == event.keyCode)
                    {
                        _inputState[ctrl_name]["State"][0] = true;
                        _inputState[ctrl_name]["State"][1] = true;
                    }
                }
            }
            
        }
        
        public function onKeyUp(event : KeyboardEvent) : void
        {
            for each (var ctrl_name : String in _controlNames)
            {
                for each (var ctrl_code : int in _inputState[ctrl_name]["Keys"])
                {
                    if (ctrl_code == event.keyCode)
                    {
                        _inputState[ctrl_name]["State"][0] = false;
                        _inputState[ctrl_name]["State"][1] = true;
                    }
                }
            }
            
        }
        
        public function onMouseDown(event : MouseEvent) : void
        {
            // TODO
        }
        
        public function onMouseUp(event : MouseEvent) : void
        {
            // TODO
        }
        
        public function onMouseMove(event : MouseEvent) : void
        {
            mousePos.x = event.stageX;
            mousePos.y = event.stageY;
            
        }
        
        public function onMouseWheel(event : MouseEvent) : void
        {
            // TODO
        }
        
        
        public function inputDown(input_name : String) : Boolean
        {
            input_name = input_name.toLowerCase();
            
            if (inputState[input_name] == null)
            {
                throw new ArgumentError("Invalid control name given");
            }
            
            // The first element in an
            // input state element is
            // whether the control is down
            return inputState[input_name]["State"][0];
            
        }
        
        public function inputUp(input_name : String) : Boolean
        {
            // This one is easy
            // It's just the opposite of
            // keyDown
            
            // We can let invalid name
            // exceptions pass through
            // as well, so we don't
            // have to do much here
            
            return !(inputDown(input_name));
            
        }
        
        public function inputPressed(input_name : String) : Boolean
        {
            input_name = input_name.toLowerCase();
            
            if (inputState[input_name] == null)
            {
                throw new ArgumentError("Invalid control name given");
            }
            
            // The first element in an
            // input state element is
            // whether the control is
            // down, and the second is
            // whether the control
            // changed on the previous
            // frame
            return inputState[input_name]["State"][0] && inputState[input_name]["State"][1];
            
        }
        
        public function inputReleased(input_name : String) : Boolean
        {
            input_name = input_name.toLowerCase();
            
            if (inputState[input_name] == null)
            {
                throw new ArgumentError("Invalid control name given");
            }
            
            // The first element in an
            // input state element is
            // whether the control is
            // down, and the second is
            // whether the control
            // changed on the previous
            // frame
            return !(inputState[input_name]["State"][0]) && inputState[input_name]["State"][1];
            
        }
        
    }
    
}
